/*
  Copyright 2011 the JSDoc Authors.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
function filter({ undocumented }) {
  return !undocumented;
}

describe('@module tag', () => {
  describe("using 'this'", () => {
    const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag.js');
    const book = docSet.getByLongname('module:bookshelf.Book')[0];
    const title = docSet.getByLongname('module:bookshelf.Book#title')[0];

    it('When a global symbol starts with "this" and is in a file with a @module tag, the symbol is documented as a member of that module.', () => {
      expect(book).toBeObject();
      expect(book.memberof).toBe('module:bookshelf');
    });

    it('When an inner symbol starts with "this" and is in a file with a @module tag, the symbol is documented as a member of its enclosing constructor.', () => {
      expect(title).toBeObject();
      expect(title.memberof).toBe('module:bookshelf.Book');
    });
  });

  describe('misc', () => {
    const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag2.js');
    const mixer = docSet.getByLongname('module:color/mixer').filter(filter)[0];
    const blend = docSet.getByLongname('module:color/mixer.blend')[0];
    const darken = docSet.getByLongname('module:color/mixer.darken')[0];

    it('When a @module tag defines a module, a symbol of kind "module" is documented', () => {
      expect(mixer).toBeObject();
      expect(mixer.kind).toBe('module');
    });

    it('When a @module tag defines a module, the module doclet does not have a "scope" property', () => {
      expect(mixer.scope).toBeUndefined();
    });

    it('When an object literal is lent to a module with a @lends tag, a member of that object literal is documented as a member of the module', () => {
      expect(blend).toBeObject();
      expect(blend.kind).toBe('function');
    });

    it('When a documented symbol is a member of a namespace "exports", it is documented as a member of the module', () => {
      expect(darken).toBeObject();
      expect(darken.kind).toBe('function');
    });
  });

  describe('virtual comments', () => {
    const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag4.js');
    const clickProperties = docSet.getByLongname('module:M1~ClickProperties')[0];
    const virtFunc = docSet.getByLongname('module:M1.VirtualComment')[0];
    const virtFunc2 = docSet.getByLongname('module:M1#VirtualComment2')[0];

    it('When a virtual comment typedef is inside a module, the typedef is a memberof the module', () => {
      expect(clickProperties.memberof).toBe('module:M1');
    });

    it('When a virtual comment typedef is inside a module, the typedef longname contains the module name', () => {
      expect(clickProperties.longname).toBe('module:M1~ClickProperties');
    });

    it('When a virtual comment typedef is inside a module, the typedef scope is "inner"', () => {
      expect(clickProperties.scope).toBe('inner');
    });

    it('When a virtual comment function is inside a module with a static scope, the function has the correct memberof and longname', () => {
      expect(virtFunc.longname).toBe('module:M1.VirtualComment');
      expect(virtFunc.memberof).toBe('module:M1');
    });

    it('When a virtual comment function is inside a module with an instance scope, the function has the correct memberof and longname', () => {
      expect(virtFunc2.longname).toBe('module:M1#VirtualComment2');
      expect(virtFunc2.memberof).toBe('module:M1');
    });
  });

  describe('"module:" namespace included in the name', () => {
    const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag5.js');
    const bookshelf = docSet.getByLongname('module:bookshelf')[0];

    it('When the name for a @module tag begins with the "module:" namespace, we remove the namespace', () => {
      expect(bookshelf).toBeObject();
      expect(bookshelf.name).toBe('bookshelf');
    });
  });

  describe('ES 2015 modules', () => {
    describe('that export a default', () => {
      describe('value type', () => {
        const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag6.js');
        const exports = docSet
          .getByLongname('module:appname')
          .filter(({ kind }) => kind === 'member')[0];

        it('When a value type is exported, it has the same name as the module longname', () => {
          expect(exports.name).toBe('module:appname');
        });
      });

      describe('object', () => {
        const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag7.js');
        const blend = docSet.getByLongname('module:color/mixer.blend')[0];

        it('When an object is exported, its members have the correct name, memberof, and kind', () => {
          expect(blend.name).toBe('blend');
          expect(blend.memberof).toBe('module:color/mixer');
          expect(blend.kind).toBe('function');
        });
      });
    });

    describe('that export named values', () => {
      const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag8.js');
      const blend = docSet.getByLongname('module:color/mixer.blend')[0];
      const lastColor = docSet.getByLongname('module:color/mixer.lastColor')[0];
      const name = docSet.getByLongname('module:color/mixer.name')[0];
      const toRgb = docSet.getByLongname('module:color/mixer.toRgb')[0];

      it('When a method is exported, it has the correct name, memberof, and kind', () => {
        expect(blend.name).toBe('blend');
        expect(blend.memberof).toBe('module:color/mixer');
        expect(blend.kind).toBe('function');
      });

      it('When a variable is exported, it has the correct name, memberof, and kind', () => {
        expect(lastColor.name).toBe('lastColor');
        expect(lastColor.memberof).toBe('module:color/mixer');
        expect(lastColor.kind).toBe('member');
      });

      it('When a constant is exported, it has the correct name, memberof, and kind', () => {
        expect(name.name).toBe('name');
        expect(name.memberof).toBe('module:color/mixer');
        expect(name.kind).toBe('constant');
      });

      it('When a symbol is exported under a different name, it has the correct name, memberof, and kind', () => {
        expect(toRgb.name).toBe('toRgb');
        expect(toRgb.memberof).toBe('module:color/mixer');
        expect(toRgb.kind).toBe('function');
      });
    });

    describe('that export another module in its entirety', () => {
      it('should not crash JSDoc', () => {
        function getDocSet() {
          jsdoc.getDocSetFromFile('test/fixtures/moduletag9.js');
        }

        expect(getDocSet).not.toThrow();
      });
    });

    describe('that export an unnamed default function', () => {
      it('should not crash JSDoc', () => {
        function getDocSet() {
          jsdoc.getDocSetFromFile('test/fixtures/moduletag10.js');
        }

        expect(getDocSet).not.toThrow();
      });
    });

    describe('that export a class', () => {
      const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduletag11.js');
      const foo = docSet.getByLongname('module:foo.Foo').filter(filter)[0];
      const testMethod = docSet.getByLongname('module:foo.Foo#testMethod')[0];

      it('should identify the correct scope for the exported class', () => {
        expect(foo).toBeObject();
      });

      it('should merge the doclet for the constructor with the doclet for the class', () => {
        expect(foo.description).toBe('Test class constructor.');
      });

      it("should identify the correct scope for the exported class's methods", () => {
        expect(testMethod).toBeObject();
      });
    });
  });
});
